﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Dynamic;
using Platform.Infrastructure;
using Platform.ObjectModel.Design;
using System.Reflection;
using FastMember;

namespace Platform.ObjectModel
{


	public abstract class DataObjectFlake_v1 : DynamicObject
	{


		/// <summary>
		/// Obtém ou define os valores dinâmicos da entidade
		/// </summary>
		internal IDictionary<string, object> Values
		{
			get;
			private set;
		}


		public object Get( DataObjectName qualifiedName )
		{
			Arguments.IsNotNull( "qualifiedName", qualifiedName );
			return this.Get( qualifiedName.Name );
		}

		public object Get( string propertyName )
		{
			object value;

			if ( this.Values.TryGetValue( propertyName, out value ) )
				return value;
			else
				return null;
		}



		public override bool TrySetMember( SetMemberBinder binder, object value )
		{
			return base.TrySetMember( binder, value );
		}

		public override bool TryGetMember( GetMemberBinder binder, out object result )
		{
			return base.TryGetMember( binder, out result );
		}
	}




	/////////////////////////////////////////////////////////////////////
	/////////////////////////////////////////////////////////////////////
	//		Antigo
	/////////////////////////////////////////////////////////////////////
	/////////////////////////////////////////////////////////////////////


	/**
	 * Exemplo de como é feita a chamada para um método dinâmico no DLR
	 * http://stackoverflow.com/questions/2079870/dynamically-adding-members-to-a-dynamic-object
	 */
	/// <summary>
	/// Representa um conjunto de dados que é mantido pelo sistema.
	/// </summary>
	/// <remarks>
	/// Realiza a implementação para interação com os objetos dinâmicos do .Net
	/// </remarks>
	public abstract class DataObjectFlake : IDynamicMetaObjectProvider
	{
		// TODO: Rever a forma de como está sendo acessada as propriedades, trazer a lógica pra dentro desse objeto, não há necessidade de ter referência para "ObjectAcessor"
		private Lazy<ObjectAccessor> accessor;

		protected DataObjectFlake()
		{
			this.Values = new Dictionary<string, object>( StringComparer.InvariantCultureIgnoreCase );
			this.accessor = new Lazy<ObjectAccessor>( () => ObjectAccessor.Create( this ), false );
		}

		/// <summary>
		/// Obtém ou define os valores dinâmicos da entidade
		/// </summary>
		internal IDictionary<string, object> Values
		{
			get;
			private set;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="fieldName"></param>
		/// <returns></returns>
		public object this[ string fieldName ]
		{
			get { return this.accessor.Value[ fieldName ]; }
			set { this.accessor.Value[ fieldName ] = value; }
		}


		public object Get( DataObjectName qualifiedName )
		{
			Arguments.IsNotNull( "qualifiedName", qualifiedName );
			return this.Get( qualifiedName.Name );
		}

		public object Get( string propertyName )
		{
			object value;

			if ( this.Values.TryGetValue( propertyName, out value ) )
				return value;
			else
				return null;
		}

		public void Set( string propertyName, object value )
		{
			this.Values[ propertyName ] = value;
		}
		
		#region IDynamicMetaObjectProvider Members

		public DynamicMetaObject GetMetaObject( Expression parameter )
		{
			return new DataObjectMetadata( parameter, this );
		}

		#endregion

		#region [ class DataObjectMetadata ]

		/// <summary>
		/// Objeto que define a estrutura do <see cref="DataObjectFlake"/> para trabalhar com linguagens dinâmicas
		/// </summary>
		internal sealed class DataObjectMetadata : DynamicMetaObject
		{
			public DataObjectMetadata( Expression parameter, DataObjectFlake value )
				//	: base( parameter, BindingRestrictions.GetInstanceRestriction( parameter, value ), value )
				: base( parameter, BindingRestrictions.Empty, value )
			{ }

			public override DynamicMetaObject BindGetMember( GetMemberBinder binder )
			{
				//var property = this.LimitType.GetProperty( binder.Name, binder.ReturnType );
				PropertyInfo property;
				bool isObjReturnType = binder.ReturnType == typeof( object );

				if ( isObjReturnType )
					property = this.LimitType.GetProperty( binder.Name );
				else
					property = this.LimitType.GetProperty( binder.Name, binder.ReturnType );

				// Cria a expressão para acessar a propriedade
				if ( property != null )
				{
					Expression getProperty =
						Expression.MakeMemberAccess(
							Expression.Convert( this.Expression, this.LimitType ),
							property );

					if ( isObjReturnType )
						getProperty = Expression.Convert( getProperty, typeof( object ) );

					return new DynamicMetaObject( getProperty, BindingRestrictions.GetTypeRestriction( this.Expression, this.LimitType ) );
				}
				else
				{
					var methodCall =
						Expression.Call(
							Expression.Convert( this.Expression, this.LimitType ),
							this.LimitType.GetMethod( "Get" ),
							Expression.Constant( binder.Name ) );

					return new DynamicMetaObject( methodCall, BindingRestrictions.GetTypeRestriction( this.Expression, this.LimitType ) );
				}
			}

			public override DynamicMetaObject BindSetMember( SetMemberBinder binder, DynamicMetaObject value )
			{
				//var property = this.LimitType.GetProperty( binder.Name, binder.ReturnType );
				var property = this.LimitType.GetProperties().FirstOrDefault( prop => String.Equals( prop.Name, binder.Name, StringComparison.InvariantCultureIgnoreCase ) );
								
				if ( property != null )
				{
					Expression valueExp = value.Expression;
					if ( value.Expression.Type != property.PropertyType )
					    valueExp = Expression.Convert( value.Expression, property.PropertyType );

					var setProperty = Expression.Assign(
							Expression.MakeMemberAccess(
								Expression.Convert( this.Expression, this.LimitType )
								, property ),
								valueExp );

					return new DynamicMetaObject( setProperty.Void(), BindingRestrictions.GetTypeRestriction( this.Expression, this.LimitType ) );
				}
				else
				{
					var methodCall =
						Expression.Call(
							Expression.Convert( this.Expression, this.LimitType ),
							this.LimitType.GetMethod( "Set" ),
							Expression.Constant( binder.Name ),
							value.Expression );

					return new DynamicMetaObject( methodCall.Void(), BindingRestrictions.GetTypeRestriction( this.Expression, this.LimitType ) );
				}
			}

			public override IEnumerable<string> GetDynamicMemberNames()
			{
				return base.GetDynamicMemberNames();
			}
		}

		#endregion
	}
}
